package com.qiangesoft.blog.system.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qiangesoft.blog.common.constants.FieldConstants;
import com.qiangesoft.blog.common.constants.RedisConstants;
import com.qiangesoft.blog.common.enums.LoginTypeEnum;
import com.qiangesoft.blog.common.mvc.PageQuery;
import com.qiangesoft.blog.common.mvc.PageResultVO;
import com.qiangesoft.blog.common.result.ResponseResult;
import com.qiangesoft.blog.system.authentication.satoken.MySaTokenListener;
import com.qiangesoft.blog.system.authentication.satoken.OnlineUser;
import com.qiangesoft.blog.system.email.EmailService;
import com.qiangesoft.blog.system.entity.*;
import com.qiangesoft.blog.system.exception.BusinessException;
import com.qiangesoft.blog.system.mapper.SysUserMapper;
import com.qiangesoft.blog.system.pojo.dto.*;
import com.qiangesoft.blog.system.pojo.vo.SysUserDetailVO;
import com.qiangesoft.blog.system.pojo.vo.UserInfoVO;
import com.qiangesoft.blog.system.redis.RedisService;
import com.qiangesoft.blog.system.service.*;
import com.qiangesoft.blog.system.strategy.context.SocialLoginStrategyContext;
import com.qiangesoft.blog.system.utils.AesEncryptUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.mail.MessagingException;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.qiangesoft.blog.common.constants.FieldConstants.LIMIT_ONE;
import static com.qiangesoft.blog.common.result.ResultCode.*;

/**
 * <p>
 * 用户基础信息 服务实现类
 * </p>
 *
 * @author qiange
 * @since 2023-06-01
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {

    private final ISysMenuService menuService;

    private final ISysRoleService roleService;

    private final SocialLoginStrategyContext socialLoginStrategyContext;

    private final EmailService emailService;

    private final RedisService redisService;

    private final ISysWebConfigService webConfigService;

    private final ISysUserRoleService userRoleService;

    @Override
    public PageResultVO<SysUser> listUser(PageQuery pageQuery, String username, Boolean state) {
        PageResultVO<SysUser> pageResultVO = new PageResultVO<>();
        Integer pageNum = pageQuery.getPageNum();
        Integer pageSize = pageQuery.getPageSize();
        pageResultVO.setPageNum(pageNum);
        pageResultVO.setPageSize(pageSize);

        Page<SysUser> userPage = this.pageUserByParam(pageNum, pageSize, username, null, null, null, state);
        pageResultVO.setTotal(userPage.getTotal());
        pageResultVO.setPages(userPage.getPages());
        pageResultVO.setResults(userPage.getRecords());
        return pageResultVO;
    }

    @Override
    public List<SysUser> listUserByParam(String username, String phoneNumber, String email, String nickname, Boolean state) {
        LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(username), SysUser::getUsername, username)
                .like(StringUtils.isNotBlank(phoneNumber), SysUser::getPhoneNumber, phoneNumber)
                .like(StringUtils.isNotBlank(nickname), SysUser::getNickname, nickname)
                .like(StringUtils.isNotBlank(email), SysUser::getEmail, email)
                .eq(!ObjectUtils.isEmpty(state), SysUser::getState, state)
                .orderByDesc(SysUser::getId);
        return baseMapper.selectList(queryWrapper);
    }

    @Override
    public Page<SysUser> pageUserByParam(Integer pageNum, Integer pageSize, String username, String phoneNumber, String email, String nickname, Boolean state) {
        Assert.notNull(pageNum, "pageNum not null");
        Assert.notNull(pageSize, "pageSize not null");
        LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(username), SysUser::getUsername, username)
                .like(StringUtils.isNotBlank(phoneNumber), SysUser::getPhoneNumber, phoneNumber)
                .like(StringUtils.isNotBlank(nickname), SysUser::getNickname, nickname)
                .like(StringUtils.isNotBlank(email), SysUser::getEmail, email)
                .eq(!ObjectUtils.isEmpty(state), SysUser::getState, state)
                .orderByDesc(SysUser::getId);
        return baseMapper.selectPage(new Page<>(pageNum, pageSize), queryWrapper);
    }

    @Override
    public SysUserDetailVO getUserById(Long id) {
        SysUser sysUser = baseMapper.selectById(id);

        SysUserDetailVO vo = new SysUserDetailVO();
        BeanUtils.copyProperties(sysUser, vo);

        List<SysRole> roleList = roleService.listRoleByUserId(id);
        vo.setRoleList(roleList);
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insertUser(UserSaveDTO userSave) {
        Integer usernameCount = baseMapper.selectCount(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, userSave.getUsername()));
        if (usernameCount > 0) {
            throw new BusinessException("用户名已存在");
        }
        Integer emailCount = baseMapper.selectCount(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, userSave.getEmail()));
        if (emailCount > 0) {
            throw new BusinessException("邮箱已存在");
        }
        String phoneNumber = userSave.getPhoneNumber();
        if (StringUtils.isNotBlank(phoneNumber)) {
            Integer phoneNumberCount = baseMapper.selectCount(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhoneNumber, userSave.getPhoneNumber()));
            if (phoneNumberCount > 0) {
                throw new BusinessException("手机号已存在");
            }
        }

        // 用户信息
        SysUser sysUser = new SysUser();
        BeanUtils.copyProperties(userSave, sysUser);
        sysUser.setPassword(AesEncryptUtils.aesEncrypt("123456"));
        baseMapper.insert(sysUser);

        // 角色信息
        List<Long> roleIdList = userSave.getRoleIdList();
        if (CollectionUtils.isEmpty(roleIdList)) {
            return;
        }
        for (Long roleId : roleIdList) {
            SysUserRole sysUserRole = new SysUserRole();
            sysUserRole.setUserId(sysUser.getId());
            sysUserRole.setRoleId(roleId);
            userRoleService.save(sysUserRole);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUser(Long id, UserSaveDTO userSave) {
        Integer usernameCount = baseMapper.selectCount(new LambdaQueryWrapper<SysUser>().ne(SysUser::getId, id).eq(SysUser::getUsername, userSave.getUsername()));
        if (usernameCount > 0) {
            throw new BusinessException("用户名已存在");
        }
        Integer emailCount = baseMapper.selectCount(new LambdaQueryWrapper<SysUser>().ne(SysUser::getId, id).eq(SysUser::getEmail, userSave.getEmail()));
        if (emailCount > 0) {
            throw new BusinessException("邮箱已存在");
        }
        String phoneNumber = userSave.getPhoneNumber();
        if (StringUtils.isNotBlank(phoneNumber)) {
            Integer phoneNumberCount = baseMapper.selectCount(new LambdaQueryWrapper<SysUser>().ne(SysUser::getId, id).eq(SysUser::getPhoneNumber, userSave.getPhoneNumber()));
            if (phoneNumberCount > 0) {
                throw new BusinessException("手机号已存在");
            }
        }

        // 用户信息
        SysUser sysUser = new SysUser();
        sysUser.setId(id);
        BeanUtils.copyProperties(userSave, sysUser);
        baseMapper.updateById(sysUser);

        // 角色信息
        LambdaQueryWrapper<SysUserRole> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SysUserRole::getUserId, id);
        userRoleService.remove(queryWrapper);

        List<Long> roleIdList = userSave.getRoleIdList();
        if (CollectionUtils.isEmpty(roleIdList)) {
            return;
        }
        for (Long roleId : roleIdList) {
            SysUserRole sysUserRole = new SysUserRole();
            sysUserRole.setUserId(id);
            sysUserRole.setRoleId(roleId);
            userRoleService.save(sysUserRole);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBatch(List<Long> ids) {
        int rows = baseMapper.deleteBatchIds(ids);
        if (rows == 0) {
            throw new BusinessException("删除失败");
        }
    }

    @Override
    public SysUserDetailVO getCurrentUserInfo() {
        return this.getUserById(StpUtil.getLoginIdAsLong());
    }


    @Override
    public List<SysMenu> getCurrentUserMenu() {
        List<SysRole> roleList = roleService.listRoleByUserId(StpUtil.getLoginIdAsLong());
        if (CollectionUtils.isEmpty(roleList)) {
            return Collections.emptyList();
        }
        List<Long> roleIds = roleList.stream().map(SysRole::getId).collect(Collectors.toList());
        List<SysMenu> menus = menuService.listMenuByRoleIds(roleIds);
        return menuService.listMenuTree(menus);
    }

    @Override
    public void updatePassword(PasswordDTO passwordDTO) {
        SysUser user = baseMapper.selectById(StpUtil.getLoginIdAsInt());
        Assert.notNull(user, ERROR_USER_NOT_EXIST.getDesc());

        boolean isValid = AesEncryptUtils.validate(user.getPassword(), passwordDTO.getOldPassword());
        Assert.isTrue(isValid, "旧密码校验不通过!");

        String newPassword = AesEncryptUtils.aesEncrypt(passwordDTO.getPassword());
        user.setPassword(newPassword);
        baseMapper.updateById(user);
    }

    @Override
    public PageResultVO<OnlineUser> listOnlineUsers(PageQuery pageQuery, String keywords) {
        int pageNo = pageQuery.getPageNum();
        int pageSize = pageQuery.getPageSize();

        List<OnlineUser> onlineUsers = MySaTokenListener.ONLINE_USERS;
        // 根据关键词过滤
        if (StringUtils.isNotBlank(keywords)) {
            onlineUsers = MySaTokenListener.ONLINE_USERS.stream().filter(item -> item.getNickname().contains(keywords)).collect(Collectors.toList());
        }
        // 排序
        onlineUsers.sort((o1, o2) -> DateUtil.compare(o2.getLoginTime(), o1.getLoginTime()));
        int fromIndex = (pageNo - 1) * pageSize;
        int toIndex = onlineUsers.size() - fromIndex > pageSize ? fromIndex + pageSize : onlineUsers.size();
        List<OnlineUser> userOnlineList = onlineUsers.subList(fromIndex, toIndex);
        log.info("memory用户数：{}", userOnlineList.size());

        PageResultVO<OnlineUser> pageResultVO = new PageResultVO();
        pageResultVO.setTotal(onlineUsers.size());
        pageResultVO.setResults(userOnlineList);
        return pageResultVO;
    }

    @Override
    public void kick(String token) {
        StpUtil.kickoutByTokenValue(token);
    }

    @Override
    public SysUser getByUsername(String username) {
        List<SysUser> userList = this.listUserByParam(username, null, null, null, null);
        if (CollectionUtils.isEmpty(userList)) {
            return null;
        }
        return userList.get(0);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void emailRegister(EmailRegisterDTO vo) {
        // 校验邮箱格式
        checkEmail(vo.getEmail());

        // 校验验证码
        checkCode(RedisConstants.EMAIL_CODE + vo.getEmail(), vo.getCode());

        // 查找用户存在性
        SysUser sysUser = this.getByUserName(vo.getEmail());
        Assert.isNull(sysUser, EMAIL_IS_EXIST.getDesc());

        // 新增用户
        SysWebConfig config = webConfigService.getOne(new QueryWrapper<SysWebConfig>().last(LIMIT_ONE));
        SysUser user = new SysUser();
        user.setEmail(vo.getEmail());
        user.setAvatar(config.getTouristAvatar());
        user.setNickname(vo.getNickname());
        user.setUsername(vo.getEmail());
        user.setLoginType(LoginTypeEnum.EMAIL.getType());
        user.setPassword(AesEncryptUtils.aesEncrypt(vo.getPassword()));
        baseMapper.insert(user);

        redisService.deleteObject(RedisConstants.EMAIL_CODE + vo.getEmail());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult updatePassword(EmailRegisterDTO vo) {

        checkEmail(vo.getEmail());
        checkCode(RedisConstants.EMAIL_CODE + vo.getEmail(), vo.getCode());

        SysUser user = getByUserName(vo.getEmail());
        Assert.notNull(user, ERROR_MUST_REGISTER.getDesc());

        user.setPassword(AesEncryptUtils.aesEncrypt(vo.getPassword()));
        boolean update = this.updateById(user);

        redisService.deleteObject(RedisConstants.EMAIL_CODE + vo.getEmail());

        return update ? ResponseResult.success("修改成功") : ResponseResult.error(ERROR_DEFAULT.getDesc());
    }

    @Override
    public ResponseResult emailLogin(EmailLoginDTO vo) {

        checkEmail(vo.getEmail());

        SysUser user = getByUserName(vo.getEmail());
        if (user == null) {
            throw new BusinessException(ERROR_MUST_REGISTER.desc);
        }
        Assert.isTrue(user.getState(), EMAIL_DISABLE_LOGIN.getDesc());

        boolean validate = AesEncryptUtils.validate(user.getPassword(), vo.getPassword());
        Assert.isTrue(validate, ERROR_PASSWORD.getDesc());

        //登录
        StpUtil.login(user.getId());

        //组装数据
        UserInfoVO userInfoVO = UserInfoVO.builder().id(user.getId()).userInfoId(user.getId()).avatar(user.getAvatar()).nickname(user.getNickname())
                .intro(user.getIntro()).email(user.getUsername()).loginType(user.getLoginType()).token(StpUtil.getTokenValue()).build();

        return ResponseResult.success(userInfoVO);
    }

    @Override
    public ResponseResult qqLogin(QQLoginDTO qqLoginDTO) {
        UserInfoVO userInfoVO = socialLoginStrategyContext.executeLoginStrategy(JSON.toJSONString(qqLoginDTO), LoginTypeEnum.QQ);
        return ResponseResult.success(userInfoVO);
    }

    @Override
    public ResponseResult weiboLogin(String code) {
        UserInfoVO userInfoVO = socialLoginStrategyContext.executeLoginStrategy(code, LoginTypeEnum.WEIBO);
        return ResponseResult.success(userInfoVO);
    }

    @Override
    public ResponseResult giteeLogin(String code) {
        UserInfoVO userInfoVO = socialLoginStrategyContext.executeLoginStrategy(code, LoginTypeEnum.GITEE);
        return ResponseResult.success(userInfoVO);
    }

    @Override
    public ResponseResult sendEmailCode(String email) {
        try {
            emailService.sendCode(email);
            return ResponseResult.success("验证码已发送，请前往邮箱查看!!");
        } catch (MessagingException e) {
            e.printStackTrace();
            return ResponseResult.error(ERROR_DEFAULT.getDesc());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult bindEmail(UserAuthDTO vo) {
        String key = RedisConstants.EMAIL_CODE + vo.getEmail();
//        checkCode(key, vo.getCode());

        SysUser userAuth = getUserAuth();
        userAuth.setEmail(vo.getEmail());
        boolean update = updateById(userAuth);
        redisService.deleteObject(key);
        return update ? ResponseResult.success("绑定邮箱成功") : ResponseResult.error(ERROR_DEFAULT.getDesc());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUser(UserAuthDTO vo) {
        SysUser userAuth = getUserAuth();
        userAuth.setNickname(vo.getNickname());
//        userAuth.setWebSite(vo.getWebSite());
        userAuth.setEmail(vo.getEmail());
        userAuth.setIntro(vo.getIntro());
        userAuth.setAvatar(vo.getAvatar());

        this.updateById(userAuth);
    }

    //---------------自定义方法开始-------------
    private SysUser getUserAuth() {
        return this.getById(StpUtil.getLoginIdAsLong());
    }

    public void checkEmail(String email) {
        boolean matches = Pattern.compile("\\w+@{1}\\w+\\.{1}\\w+").matcher(email).matches();
        Assert.isTrue(matches, EMAIL_ERROR.getDesc());
    }

    public SysUser getByUserName(String username) {
        return this.getOne(new QueryWrapper<SysUser>().eq(FieldConstants.USERNAME, username));
    }

    public SysUser getByUserId(Long userId) {
        return this.getById(userId);
    }

    private void checkCode(String key, String sourCode) {
        Object code = redisService.getCacheObject(key);
        Assert.isTrue(code != null && code.equals(sourCode), ERROR_EXCEPTION_MOBILE_CODE.getDesc());
    }
}
